package batch.base;

import java.io.PrintStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import batch.controller.JobUtil;
import batch.status.Job;
import batch.status.JobDetail;
import batch.status.JobDetailStatus;
import batch.status.JobFileStatus;
import batch.status.JobState;
import batch.status.JobStatus;
import core.config.Factory;
import core.exception.PhysicalException;
import core.exception.ThrowableUtil;
import core.util.DateUtil;
import core.util.NumberUtil;

/**
 * バッチステータス確認／更新クラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class BatchStatus {

	/** 起動パラメタ（開始確認） */
	public static final String JOB_START = "-start";
	/** 起動パラメタ（終了確認） */
	public static final String JOB_END = "-end";
	/** 起動パラメタ（継続） */
	public static final String JOB_CONT = "-continue";
	/** 起動パラメタ（バッチジョブクリア） */
	public static final String JOB_CLEAR = "-clear";

	/** ログ出力用クラス */
	private static final Logger LOGGER = LogManager.getLogger(BatchStatus.class);

	/** 削除対象日付（日数）のデフォルト値 */
	private int interval = 6;
	/** ジョブID */
	private String jobId = "";
	/** ジョブ名称 */
	private String jobName = "";
	/** ジョブステータス */
	private int status = 0;
	/** ジョブ連番 */
	private long jobSeq = 0;
	/** ユーザID */
	private String uid = null;
	/** IP */
	private String ip = null;
	/** 処理時間 */
	private final Timestamp dateTime = DateUtil.getDateTime();
	/** 削除件数リスト */
	private final List<Integer> clearedList = new ArrayList<>();

	/**
	 * バッチmain処理
	 *
	 * @param args 起動パラメタ
	 */
	public static void main(final String... args) {
		int ret = new BatchStatus().perform(args);
		Runtime.getRuntime().exit(ret);
	}

	/**
	 * 削除件数リスト取得
	 *
	 * @return 削除件数リスト
	 */
	public List<Integer> getCleared() {
		return this.clearedList;
	}

	/**
	 * 処理実行
	 *
	 * @param args 引数
	 * @return 処理結果
	 */
	public int perform(final String... args) {
		int ret = Batch.RET_SUCCESS;
		try {
			// 引数チェック
			String[] prms = checkArgs(args);

			if (JOB_START.equals(args[0])) {
				// ジョブ管理状態確認
				ret = checkJobStart(prms);
			} else if (JOB_CONT.equals(args[0])) {
				// ジョブ詳細管理状態確認
				ret = checkLastJobDetail(prms);
			} else if (JOB_END.equals(args[0])) {
				// ジョブ詳細管理状態確認
				ret = checkAllJobDetail(prms);
			} else {
				// バッチジョブクリア
				clearJob();
			}

		} catch (final IllegalArgumentException ex) {
			printUsage(BatchStatus.class.getName());
			Runtime.getRuntime().exit(Batch.RET_PARAM_ERROR);
		} catch (final Throwable th) {
			ThrowableUtil.error(th);
			ret = Batch.RET_ENV_ERROR;
		}

		return ret;
	}

	/**
	 * 使用方法表示
	 * @param name クラス名
	 */
	public static void printUsage(final String name) {
		println(System.err, String.format("java %s -clear [n] %n", name));
		println(System.err, String.format("java %s -start -o jobseq %n", name));
		println(System.err, String.format("java %s -start jobid jobname [-u userid] %n", name));
		println(System.err, String.format("java %s -continue -o jobseq %n", name));
		println(System.err, String.format("java %s -end [status message] -o jobseq %n", name));
	}

	/**
	 * 出力処理
	 * @param os PrintStream
	 * @param val 出力文字列
	 */
	private static void println(final PrintStream os, final String val) {
		if (os != null) {
			os.println(val);
		}
	}

	/**
	 * パラメタチェック
	 *
	 * @param args パラメタ
	 * @return 引数
	 */
	public String[] checkArgs(final String... args) {
		BatchParameter param = new BatchParameterImpl(args);

		String[] prms = param.getParameter();
		if (prms.length == 0) {
			throw new IllegalArgumentException();
		}

		if (JOB_CLEAR.equalsIgnoreCase(prms[0])) {
			if (1 < prms.length) {
				// 数値チェック
				int num = NumberUtil.toInt(prms[1], -1);
				if (num < 0) {
					throw new IllegalArgumentException(prms[1]);
				}
				setInterval(num);
			}
			return prms;
		} else if (JOB_START.equalsIgnoreCase(prms[0])) {
			if (prms.length == 3) {
				setJobId(prms[1]);
				setJobName(prms[2]);
			} else if (prms.length != 1) {
				throw new IllegalArgumentException();
			}
			setUid(param.getUid());

		} else if (JOB_END.equalsIgnoreCase(prms[0])) {
			if (prms.length != 1 && prms.length != 3) {
				throw new IllegalArgumentException();
			}
		}

		// JobSeqチェック
		if (param.getJobSeq() == 0) {
			if (!JOB_START.equalsIgnoreCase(prms[0])) {
				throw new IllegalArgumentException();
			}
		}

		this.jobSeq = param.getJobSeq();

		return prms;
	}

	/**
	 * ホスト名取得
	 * @return ホスト名
	 */
	private static String getHostName() {
		try {
			InetAddress localhost = InetAddress.getLocalHost();
			return localhost.getHostName();
		} catch (final UnknownHostException ex) {
			LogManager.getLogger().error(ex.getMessage(), ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * 開始状態チェック
	 *
	 * @param prms パラメタ
	 * @return バッチ終了ステータス
	 */
	public int checkJobStart(final String... prms) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.valueOf(prms.length));
		}

		int ret = Batch.RET_SUCCESS;
		try (Connection conn = JobUtil.getConnection()) {

			if (this.jobSeq != 0L) {
				// ジョブ管理状態取得
				Job job = getJobStatus(conn);
				if (job == null) {
					ret = Batch.RET_ENV_ERROR;
					return ret;
				}

				if (JobState.isCancel(job.getJobSts())) {
					ret = Batch.RET_CANCELED;
				} else if (!JobState.ID_B_SH_START.equals(job.getJobSts())) {
					ret = Batch.RET_ENV_ERROR;
				} else {
					// ジョブ管理状態確認・更新
					JobStatus js = Factory.create(JobStatus.class);
					if (js.updateJobStatus(conn, this.jobSeq,
									job.getHostId(), "", JobState.ID_B_EDIT) != 1) {
						ret = Batch.RET_ENV_ERROR;
					}
				}
			} else {
				// ジョブ管理テーブル作成
				JobStatus js = Factory.create(JobStatus.class);
				long jseq = js.insertJob(conn, newJobInstance());
				if (jseq <= 0) {
					ret = Batch.RET_ENV_ERROR;
					return ret;
				}
				// ジョブ連番表示
				// バッチまたはシェルが標準入力から取得
				println(System.out, String.valueOf(jseq));

				this.jobSeq = jseq;
			}

			conn.commit();

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			ret = Batch.RET_DB_ERROR;
		}

		return ret;
	}

	/**
	 * ジョブレコード作成
	 *
	 * @return ジョブレコード
	 */
	private Job newJobInstance() {
		Job job = new Job();
		job.setJobId(getJobId());
		job.setUid(getUid());
		job.setDateTime(this.dateTime);
		job.setGamenParam("");
		job.setJobName(getJobName());
		job.setIp(getIp());
		job.setHostId(getHostName());
		job.setJobSts(JobState.ID_B_EDIT.value());
		job.setRemark("");
		return job;
	}

	/**
	 * ジョブ継続処理
	 *
	 * @param prms パラメタ
	 * @return バッチ終了ステータス
	 */
	public int checkLastJobDetail(final String... prms) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug(String.valueOf(prms.length));
		}

		int ret = Batch.RET_FAILED;
		try (Connection conn = JobUtil.getConnection()) {

			// ジョブ管理状態取得
			Job job = getJobStatus(conn);
			if (job == null) {
				ret = Batch.RET_ENV_ERROR;
				return ret;
			}

			// ジョブ詳細管理状態取得
			// 逐次を前提
			List<JobDetail> dtllst = updateJobDetail(conn, job.getJobSts());
			if (dtllst == null || dtllst.isEmpty()) {
				ret = Batch.RET_ENV_ERROR;
				return ret;
			}

			// 最終バッチ状態判定
			int dtlsts = dtllst.get(0).getBatSts();
			if (JobState.isEnd(dtlsts)) {
				ret = Batch.RET_SUCCESS;
			} else if (JobState.isCancel(dtlsts)) {
				ret = Batch.RET_CANCELED;
			}

			conn.commit();

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			ret = Batch.RET_DB_ERROR;
		}
		return ret;
	}

	/**
	 * ジョブ終了処理
	 *
	 * @param prms パラメタ
	 * @return バッチ終了ステータス
	 */
	public int checkAllJobDetail(final String... prms) {
		int ret = Batch.RET_SUCCESS;

		try (Connection conn = JobUtil.getConnection()) {

			// ジョブ管理状態取得
			Job job = getJobStatus(conn);
			if (job == null) {
				ret = Batch.RET_ENV_ERROR;
				return ret;
			}

			if (prms != null && 2 < prms.length) {
				job.setJobSts(NumberUtil.toInt(prms[1], 0));
				job.setMsgTxt(prms[2]);
			} else {
				ret = setJobStatus(conn, job);
			}

			// ジョブ管理状態更新
			JobStatus js = Factory.create(JobStatus.class);
			if (js.updateJobStatus(conn, this.jobSeq, job.getHostId(),
							job.getMsgTxt(), JobState.valueOf(job.getJobSts())) != 1) {
				ret = Batch.RET_FAILED;
			}

			conn.commit();

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			ret = Batch.RET_DB_ERROR;
		}

		return ret;
	}

	/**
	 * ジョブステータス取得
	 *
	 * @param conn コネクション
	 * @return ジョブステータス
	 */
	private Job getJobStatus(final Connection conn) {
		JobStatus js = Factory.create(JobStatus.class);
		Job job = js.getJobWithLock(conn, this.jobSeq);
		if (job != null) {
			setJobId(job.getJobId());
			setStatus(job.getJobSts());
			setUid(job.getUid());
			this.ip = job.getIp();
		}

		return job;
	}

	/**
	 * ジョブステータス設定
	 *
	 * @param job ジョブ管理オブジェクト
	 * @param conn コネクション
	 * @return 処理結果
	 */
	private int setJobStatus(final Connection conn, final Job job) {

		// ジョブ詳細管理状態取得
		List<JobDetail> dtllst = updateJobDetail(conn, job.getJobSts());
		if (dtllst == null || dtllst.isEmpty()) {
			LOGGER.info("detail list is empty.");
			job.setMsgTxt("");
			job.setJobSts(JobState.ID_B_INVALID.value());
			return Batch.RET_SUCCESS;
		}

		job.setMsgTxt("");
		job.setJobSts(JobState.ID_B_NEND.value());

		// ジョブ終了状態確定
		int ret = -1;
		for (final JobDetail jd : dtllst) {
			int jdsts = jd.getBatSts();
			if (JobState.isError(jdsts)) {
				ret = Batch.RET_FAILED;
				job.setJobSts(JobState.ID_B_INVALID.value());
				break;
			} else if (JobState.isCancel(jdsts)) {
				ret = Batch.RET_CANCELED;
				job.setJobSts(JobState.ID_B_CANCEL.value());
			} else if (JobState.ID_B_NODATA.equals(jdsts) && ret == -1) {
				ret = Batch.RET_NODATA;
				job.setJobSts(JobState.ID_B_NODATA.value());
			} else if (JobState.ID_B_NEND.equals(jdsts)
							&& (ret == -1 || ret == Batch.RET_NODATA)) {
				ret = Batch.RET_SUCCESS;
				job.setJobSts(JobState.ID_B_NEND.value());
			} else if (JobState.ID_B_WARNING.equals(jdsts)
							&& (ret == -1 || ret == Batch.RET_NODATA || ret == Batch.RET_SUCCESS)) {
				ret = Batch.RET_WARNING;
				job.setJobSts(JobState.ID_B_WARNING.value());
			}
		}

		return ret;
	}

	/**
	 * ジョブ詳細管理更新
	 *
	 * @param conn コネクション
	 * @param jobsts ジョブステータス
	 * @return ジョブ詳細管理リスト
	 */
	private List<JobDetail> updateJobDetail(final Connection conn, final int jobsts) {
		JobDetailStatus jds = Factory.create(JobDetailStatus.class);
		List<JobDetail> dtllst = jds.selectJobDetails(conn, this.jobSeq);
		if (dtllst != null && !dtllst.isEmpty()) {
			int dtlno = dtllst.size();
			int dtlsts = dtllst.get(0).getBatSts();

			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("JOB_SEQ:" + this.jobSeq);
				LOGGER.debug("JOB_DTL_SEQ:" + dtlno);
				LOGGER.debug("JOB_DTL_STS:" + dtlsts);
			}

			// ジョブ詳細管理が処理中のとき
			if (JobState.ID_B_EDIT.equals(dtlsts)) {
				JobDetail jd = dtllst.get(0);
				if (JobState.isCancel(jobsts)) {
					jds.updateJobDetail(conn, this.jobSeq, dtlno,
									null, JobState.ID_B_CANCEL, this.dateTime);
					jd.setBatSts(JobState.ID_B_CANCEL.value());
				} else {
					jds.updateJobDetail(conn, this.jobSeq, dtlno,
									null, JobState.ID_B_INVALID, this.dateTime);
					jd.setBatSts(JobState.ID_B_INVALID.value());
				}
			}
		}
		return dtllst;
	}

	/**
	 * バッチジョブクリア
	 *
	 */
	public void clearJob() {
		// 削除対象日付を計算
		long dt = this.dateTime.getTime() - (getInterval() * TimeUnit.DAYS.toMillis(1));

		int jobdel = 0;
		int dtldel = 0;
		int fildel = 0;
		int rmfile = 0;

		JobStatus js = Factory.create(JobStatus.class);
		JobDetailStatus jds = Factory.create(JobDetailStatus.class);
		JobFileStatus jfs = Factory.create(JobFileStatus.class);

		boolean committed = false;
		try (Connection conn = JobUtil.getConnection()) {
			List<Job> ret = js.getJobListByTimestamp(conn, null, new Timestamp(dt));
			if (ret != null) {
				for (final Job job : ret) {
					committed = false;
					long seq = job.getJobSeq();
					// 作成ファイルとレコードの削除
					rmfile += jfs.deleteFiles(conn, seq);
					// ファイル管理削除
					fildel += jfs.deleteJobFile(conn, seq);
					// ジョブ管理テーブル削除
					jobdel += js.deleteJob(conn, seq);
					// ジョブ詳細管理削除
					dtldel += jds.deleteJobDetail(conn, seq);

					conn.commit();
					committed = true;
				}
			}

			if (committed) {
				this.clearedList.add(Integer.valueOf(jobdel));
				this.clearedList.add(Integer.valueOf(dtldel));
				this.clearedList.add(Integer.valueOf(fildel));
				this.clearedList.add(Integer.valueOf(rmfile));
			}

		} catch (final SQLException ex) {
			ThrowableUtil.error(ex);
			throw new PhysicalException(ex);
		}
	}

	/**
	 * @return Returns the interval.
	 */
	public int getInterval() {
		return this.interval;
	}
	/**
	 * @param val The interval to set.
	 */
	public void setInterval(final int val) {
		this.interval = val;
	}

	/**
	 * @return Returns the jobId.
	 */
	public String getJobId() {
		return this.jobId;
	}
	/**
	 * @param val The jobId to set.
	 */
	public void setJobId(final String val) {
		this.jobId = val;
	}

	/**
	 * @return Returns the jobName.
	 */
	public String getJobName() {
		return this.jobName;
	}
	/**
	 * @param val The jobName to set.
	 */
	public void setJobName(final String val) {
		this.jobName = val;
	}

	/**
	 * @return Returns the status.
	 */
	public int getStatus() {
		return this.status;
	}
	/**
	 * @param val The status to set.
	 */
	public void setStatus(final int val) {
		this.status = val;
	}

	/**
	 * @return Returns the jobSeq.
	 */
	public long getJobSeq() {
		return this.jobSeq;
	}

	/**
	 * @return Returns the uid.
	 */
	public String getUid() {
		return Objects.toString(this.uid, this.jobId);
	}
	/**
	 * @param val the uid to set
	 */
	public void setUid(final String val) {
		this.uid = val;
	}

	/**
	 * @return Returns the ip.
	 */
	public String getIp() {
		if (this.ip == null) {
			try {
				this.ip = InetAddress.getLocalHost().getHostAddress();
			} catch (final UnknownHostException ex) {
				LogManager.getLogger().warn(ex.getMessage(), ex);
			}
		}
		return this.ip;
	}
}
